Swift之交换/替换方法

2019-01-30

运行环境

  • Xcode 10.1
  • Swift 4.2

背景知识

Swift 是一种强类型语言。即默认类型是安全的静态类型。纯Swift类的函数调用已经不再是OC的运行时发送消息,而是类似于C++的vtable,在编译时就确定了调用哪个函数,所以没法通过runtime获取方法,属性。Swift中的动态性可以通过OC运行时来获得,动态性最常用的就是方法替换(Method Swizzling)。

Swift中的动态修饰符

  • @objc: 将`Swift 函数 暴露给 OC运行时,但是它仍然不能保证完全动态,编译器会尝试去对它做静态优化
  • @dynamic: 动态功能修饰符,它能保证函数,属性可以获得动态性

注意:继承自NSObjectSwift类,其继承自父类的方法具有动态性,因为是NSObject中的方法,其他在Swift中自定义方法,属性需要加@objc@dynamic 必须同时加上者两个修饰符获得动态性,否则要报错"'dynamic' instance method 'sayHello' must also be '@objc'" 。若方法的参数,属性类型为Swift特有的,无法映射到OC的类型上(例如,Character,Tuple),不能添加@dynamic

定义一个需要替换方法实现的类(不继承NSObject)

1
2
3
4
5
6
class SayHello {
// 需要添加dynamic修饰,否则无法使用runtime
@objc dynamic func sayHello(_ name: String) {
print("Hello \(name)!")
}
}

两种方案

  • 方案一:用Swift中的unsafeBitCast 实现

扩展一个函数 swizzleSayHello()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
extension SayHello {
class func swizzleSayHello() {
let originalSlector = #selector(SayHello.sayHello(_:))
let originalMethod = class_getInstanceMethod(SayHello.self, originalSlector)!
let originalImplementation = method_getImplementation(originalMethod)
//id (*IMP)(id, SEL, ...)
typealias originalClosureType = @convention(c) (AnyObject,Selector,String) -> Void
let originalClosure:originalClosureType = unsafeBitCast(originalImplementation, to: originalClosureType.self)
let newBlock:@convention(block)(AnyObject,String) -> Void = {
obj, name in
var name__ = name
name__ = name + ",nice to meet you"
return originalClosure(obj,originalSlector,name__)
}
//Its signature should be: method_return_type ^(id self, method_args...).
let newImplementation = imp_implementationWithBlock(unsafeBitCast(newBlock, to: AnyObject.self))
print(newImplementation)
method_setImplementation(originalMethod, newImplementation)
}
}

调用函数

1
2
SayHello.swizzleSayHello()
SayHello().sayHello("Jack")

打印:

1
Hello Jack,nice to meet you!
  • 方案二:用method_exchangeImplementations实现

同样扩展一个函数 swizzleSayHello() 以及实现一个目标函数swizzledSayHello(_ name: String)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
extension SayHello {
//Argument of '#selector' refers to instance method 'swizzleSayHello' that is not exposed to Objective-C
//Add '@objc' to expose this instance method to Objective-C
//@objc 表示 暴露 方法给 oc 调用
@objc private func swizzledSayHello(_ name: String) -> Void {
var name__ = name
name__ = name + ",nice to meet you"
return swizzledSayHello(name__)
}
class func swizzledSayHello() {

let originalSelctor = #selector(SayHello.sayHello(_:))
let swizzledSelector = #selector(SayHello.swizzledSayHello(_:))
swizzleMethod(for: SayHello.self, originalSelector: originalSelctor, swizzledSelector: swizzledSelector)
}
private class func swizzleMethod(for aClass: AnyClass, originalSelector: Selector,swizzledSelector: Selector) {
let originalMethod = class_getInstanceMethod(aClass, originalSelector)
let swizzledMethod = class_getInstanceMethod(aClass, swizzledSelector)
let didAddMethod = class_addMethod(aClass, originalSelector, method_getImplementation(swizzledMethod!), method_getTypeEncoding(swizzledMethod!))
if didAddMethod {
class_replaceMethod(aClass, swizzledSelector, method_getImplementation(originalMethod!), method_getTypeEncoding(originalMethod!))
} else {
method_exchangeImplementations(originalMethod!, swizzledMethod!)
}
}
}

调用函数

1
2
SayHello.swizzledSayHello()
SayHello().sayHello("Jack")

打印:

1
Hello Jack,nice to meet you!

最后

@convention 关键字可以参考 @convention 以及 @convention